import * as zrender from "zrender";
import Stack from "./stack";
import Frame from "./frame";
import RelateLink from "./relateLink";
import * as cmd from "./cmd";
import shape from "./shape";
import NodeGroup from "./nodeGroup";
import { doubleHandler, guid } from "@/utils";

class FlowEditor extends zrender.Group {
  constructor(el, data = {}) {
    super();
    this.zr = zrender.init(el, { renderer: "canvas" });
    this.id = guid();
    this.data = data;
    this.stack = new Stack(data.stackStep || 50);

    this.bindingEl = el;
    this.nodes = [];
    this.edges = [];
    this.groups = [];

    this.status = "";

    this.frame = new Frame(this);

    //框选
    this.selectFrame = new zrender.Rect({
      style: {
        fill: "#1890ff",
        opacity: 0.1,
      },
      shape: {
        x: 0,
        y: 0,
        width: 0,
        heigt: 0,
      },
      z: 10000,
    });

    this.zr.add(this.selectFrame);
    this.selectFrame.hide();

    this.zr.add(this);

    //拖动 group 或 多个节点的浮层
    this.cover = new zrender.Rect({
      style: {
        fill: "rgb(231, 247, 254)",
        stroke: "rgb(178, 222, 254)",
        opacity: 0.6,
      },
      shape: {
        width: 0,
        height: 0,
      },
      z: 100000,
    });

    this.cover.hide();
    this.add(this.cover);

    this.commandChange();
    this.initEvent();
  }

  $on(name, fn) {
    if (!(fn instanceof Function)) {
      throw new Error("fn is not a function");
    }

    this.zr.on(name, fn);
  }

  $emit(name, data) {
    this.zr.trigger(name, data);
  }

  offEvent() {
    this.zr.off();
  }

  init(initData, show) {
    this.nodes = [];
    this.edges = [];
    this.groups = [];
    this.removeAll();
    this.add(this.cover);
    this.attr({ position: [0, 0] });
    if (initData?.type === "cicadaFlow") {
      const { node, group, edge } = initData;

      node.forEach((item) => {
        const s = shape.getShape(item.command, item, show);
        s.setData(item);
        if (!show) {
          s.createAnchors();
          s.anchor.refresh();
        }
        this.addNode(s);
      });

      group.forEach((item) => {
        const nodes = item.nodes.map((nodeData) => this.getNodeById(nodeData.node));

        const g = new NodeGroup(nodes);
        g.setHeadStyle(item.headStyle);
        g.setGroupStyle(item.groupStyle);
        g.setId(item.id);
        g.changeHead(item.showHead);

        if (item.locked) {
          g.setBox(item.box).toggleLock();
        }

        this.addGroup(g);
      });

      group.forEach((item) => {
        if (item.topGroup) {
          const tg = this.getGroupById(item.topGroup);
          const g = this.getGroupById(item.id);
          g.topGroup = tg;
          tg.nodes.push(g);
          tg.refresh();
        }
      });

      edge.forEach((data) => {
        const line = new RelateLink();
        line.setData(data);
        if (data.fromNode) {
          const fromNode = this.getItemById(data.fromNode);
          const anchor = fromNode.getAnchorByIndex(data.fromAnchor);
          line.setFromNode(fromNode);
          line.setFromAnchor(anchor);
        }

        if (data.endNode) {
          const toNode = this.getItemById(data.endNode);
          const anchor = toNode.getAnchorByIndex(data.toAnchor);
          line.setToNode(toNode);
          line.setToAnchor(anchor);
        }

        this.addEdge(line);
      });
    } else {
      throw new Error("不支持的数据");
    }
  }

  initEvent() {
    let drag = false;
    let startX = 0;
    let startY = 0;
    let isCreateLink = false;
    let edge;
    let changeEdge = null;
    let circle;
    let anchor;
    let dx = 0;
    let dy = 0;
    let pos = null;

    this.zr.on("selectNode", (e) => this.selectItem(e.node));
    this.zr.on("clearSelectItems", () => this.clearSelectItems());
    this.zr.on("judgeWidthGroup", () => this.judgeWidthGroup());
    this.zr.on("viewProGroup", () => this.viewProGroup());
    this.zr.on("hideFrame", () => this.frame.hide());
    this.zr.on("selectFrame", () => (this.status = "selectFrame"));

    this.zr.on("moveGroup", (e) => {
      this.execute("moveNode", { dx, dy }, e.group.getAllItems());
    });

    this.zr.on("refreshEdge", (e) => {
      const { node } = e;
      let edges = null;

      if (node.nodeType === "node") {
        edges = this.getEdgesByNode(node);
      } else {
        edges = this.getEdgesByGroup(node);
      }

      edges.forEach((link) => {
        if (link.fromNode.id === node.id) {
          link.setFromPoint(node.getAnchorsByIndex(link.fromAnchor.point.index));
          link.refresh();
        } else if (link.toNode.id === node.id) {
          link.setToPoint(node.getAnchorsByIndex(link.toAnchor.point.index));
          link.refresh();
        }
      });
    });

    this.zr.on("moveNode", (e) => {
      const { node } = e;
      let selectItem = this.getSelectNodes();

      if (node && selectItem.indexOf(node) === -1) {
        selectItem = [...selectItem, node];
      }

      this.execute("moveNode", { dx, dy }, selectItem);
    });

    this.zr.on("removeNodeFromGroup", (e) => {
      this.execute("removeNodeFromGroup", { node: e.node });
    });

    this.zr.on("addNodeToGroup", (e) => {
      this.execute("addNodeToGroup", e);
    });

    this.zr.on("resizeNode", (e) => {
      this.execute("resizeNode", e);
    });

    this.zr.on("refreshSelectGroup", () => {
      (this.selectGroups || []).forEach((item) => item.refresh());
    });

    const mouseMove = () => {
      if (this.status === "selectFrame") {
        this.selectFrame.attr({ shape: { width: dx, height: dy } });
        this.multipleSelect();
      } else {
        this.attr({ position: [pos[0] + dx, pos[1] + dy] });
      }
    };

    const mouseUp = () => {
      this.selectFrame.attr({ shape: { x: 0, y: 0, width: 0, heigt: 0 } });
      this.status = "";
      this.selectFrame.hide();
      document.removeEventListener("mousemove", mouseMove);
      document.removeEventListener("mouseup", mouseUp);
    };

    this.zr.on("mousedown", (e) => {
      const { offsetX, offsetY } = e;
      drag = true;
      startX = offsetX;
      startY = offsetY;
      pos = this.position.slice();
      if (!e.target) {
        this.zr.trigger("clearSelectItems");

        if (this.status === "selectFrame") {
          this.selectFrame.attr({ shape: { x: startX, y: startY } });
          this.selectFrame.show();
        }

        document.addEventListener("mousemove", mouseMove);
        document.addEventListener("mouseup", mouseUp);
        return;
      }

      if (e.target.mark && e.target.mark === "anchor") {
        if (isCreateLink && edge) {
          this.remove(edge);
          this.status = "";
          isCreateLink = false;
          return;
        }

        anchor = e.target;
        this.status = "isCreateLink";
        isCreateLink = true;
        edge = new RelateLink(anchor.node);
        edge.setFromPoint({ ...anchor.point });
        edge.fromAnchor = anchor;
        this.add(edge);
      }

      const activeItem = this.getSelectNodes();

      activeItem.forEach((item) => (item.oldPos = item.position.slice()));

      if (e.target.nodeType === "node") {
        this.multipleSelectEdges();
      }

      if (e.target.edge) {
        this.status = "changeEdge";
        changeEdge = e.target.edge;
        circle = e.target;
      }
    });

    this.zr.on("mousemove", (e) => {
      dx = e.offsetX - startX;
      dy = e.offsetY - startY;
      this.showAnchor(e.offsetX, e.offsetY);

      if (!drag) return;

      if (isCreateLink || this.status === "changeEdge") {
        edge.move(e.offsetX - pos[0], e.offsetY - pos[1]);
      }

      if (this.status === "selectFrame") return;

      if (!isCreateLink && e.target && e.target.nodeType === "node") {
        if (["resizeNode", "changeEdge"].includes(this.status)) return;

        this.moveNode(dx, dy);
        this.viewProGroup();
      }

      if (this.status === "changeEdge" && changeEdge && circle) {
        const { cx, cy } = circle.oldCenter;
        circle.attr({ shape: { cx: cx + dx, cy: cy + dy } });

        if (circle.edgeType === "from") {
          changeEdge.setFromPoint({ x: cx + dx, y: cy + dy }, true);
        } else {
          changeEdge.setToPoint({ x: cx + dx, y: cy + dy });
        }
      }
    });

    this.zr.on("mouseup", (e) => {
      if (e.target?.mark === "anchor" && isCreateLink) {
        if (e.target === anchor) {
          this.removeEdge(edge);
          this.status = "";
          return;
        } else {
          try {
            edge.setToNode(e.target.node);
            edge.setToPoint({ ...e.target.point });
            edge.toAnchor = e.target;
            this.execute("addEdge", { edge });
          } catch (error) {
            console.error(error);
            this.removeEdge(edge);
            edge = null;
            this.status = "";
          } finally {
            edge = null;
            this.status = "";
          }
        }
      }

      if (e.target?.mark === "anchor" && this.status === "changeEdge") {
        /**
         * 调整关联线段顶点 分为 无——>有，有——>无，无——>无,有——>有;
         */

        if (circle.edgeType === "from") {
          if (changeEdge?.fromNode) {
            if (changeEdge.fromAnchor != e.target) {
              const { fromNode, fromAnchor } = changeEdge;

              this.execute("changeEdge", {
                edge: changeEdge,
                type: "changeAnchor",
                position: "from",
                mark: "anchor",
                oldNode: fromNode,
                oldAnchor: fromAnchor,
                newNode: e.target.node,
                newAnchor: e.target,
              });
            }
          } else {
            this.execute("changeEdge", {
              edge: changeEdge,
              type: "changeAnchor",
              position: "from",
              mark: "point",
              oldCenter: { ...circle.oldCenter },
              newCenter: {
                x: circle.shape.cx,
                y: circle.shape.cy,
              },
              newNode: e.target.node,
              newAnchor: e.target,
            });
          }
        } else {
          if (changeEdge?.toNode) {
            if (changeEdge.toAnchor != e.target) {
              const { toNode, toAnchor } = changeEdge;

              this.execute("changeEdge", {
                edge: changeEdge,
                type: "changeAnchor",
                position: "to",
                mark: "anchor",
                oldNode: toNode,
                oldAnchor: toAnchor,
                newNode: e.target.node,
                newAnchor: e.target,
              });
            }
          } else {
            this.execute("changeEdge", {
              edge: changeEdge,
              type: "changeAnchor",
              position: "to",
              mark: "point",
              oldCenter: { ...circle.oldCenter },
              newCenter: {
                x: circle.shape.cx,
                y: circle.shape.cy,
              },
              newNode: e.target.node,
              newAnchor: e.target,
            });
          }
        }

        changeEdge.refresh();
        this.status = "";
        circle = null;
        changeEdge = null;
      }

      if (this.status === "changeEdge") {
        if (circle.edgeType === "from") {
          if (changeEdge.fromNode) {
            const { fromNode, fromAnchor } = changeEdge;

            changeEdge.removeFromNode();
            this.execute("changeEdge", {
              edge: changeEdge,
              type: "changeAnchor",
              position: "from",
              mark: "anchor",
              oldNode: fromNode,
              oldAnchor: fromAnchor,
              oldCenter: { ...circle.oldCenter },
              newCenter: {
                x: circle.shape.cx,
                y: circle.shape.cy,
              },
            });
          } else {
            this.execute("changeEdge", {
              edge: changeEdge,
              type: "changeAnchor",
              position: "from",
              mark: "point",
              oldCenter: { ...circle.oldCenter },
              newCenter: {
                x: circle.shape.cx,
                y: circle.shape.cy,
              },
            });
          }
        } else {
          if (changeEdge?.toNode) {
            if (changeEdge.toNode) {
              const { toNode, toAnchor } = changeEdge;

              changeEdge.removeToNode();

              this.execute("changeEdge", {
                edge: changeEdge,
                type: "changeAnchor",
                position: "to",
                mark: "anchor",
                oldNode: toNode,
                oldAnchor: toAnchor,
                oldCenter: { ...circle.oldCenter },
                newCenter: {
                  x: circle.shape.cx,
                  y: circle.shape.cy,
                },
              });
            } else {
              this.execute("changeEdge", {
                edge: changeEdge,
                type: "changeAnchor",
                position: "to",
                mark: "point",
                oldCenter: { ...circle.oldCenter },
                newCenter: {
                  x: circle.shape.cx,
                  y: circle.shape.cy,
                },
              });
            }
          }
        }

        this.status = "";
        circle = null;
        changeEdge = null;
      }

      if (edge) this.remove(edge);

      if (this.selectFrame.ignore) {
        this.updateNodeAnchor();
      } else {
        this.zr.trigger("selectNodes");
      }

      isCreateLink = false;
      drag = false;
      startX = 0;
      startY = 0;
    });

    const [doubleResize] = doubleHandler(() => {
      const width = this.bindingEl.offsetWidth;
      const height = this.bindingEl.offsetHeight;

      this.zr.resize({ width, height });
    }, 250);

    window.addEventListener("resize", doubleResize);
  }

  execute(name, data, nodes) {
    const cmds = [];
    switch (name) {
      case "addShape":
        try {
          data.x -= this.position[0];
          data.y -= this.position[1];

          if (data.image) data.command = "image";
          const node = shape.getShape(data.command, data);
          this.stack.execute(new cmd.AddNodes([node], this));
        } catch (e) {
          console.error(e);
        }
        break;

      case "moveNode":
        if (nodes.length) {
          nodes.forEach((item) => {
            if (item.nodeType !== "node") return;
            const oldPos = item.oldPos.slice();
            const newPos = item.position.slice();
            const movePosCmd = new cmd.MovePosCmd(item, oldPos, newPos);
            cmds.push(movePosCmd);
          });

          if (cmds.length) {
            const manycmds = new cmd.ManyCmd(cmds);
            this.stack.execute(manycmds);
          }
        }
        break;

      case "addEdge":
        this.stack.execute(new cmd.AddEdgeCmd(data.edge, this));
        break;

      case "createGroup":
        this.stack.execute(new cmd.CreateGroup(data.group, data.topGroup, this));
        break;

      case "cancelGroup":
        this.stack.execute(new cmd.CancelGroup(data.group, this.zr));
        break;

      case "removeNodeFromGroup":
        this.stack.execute(new cmd.RemoveNodeFromGroup(data.node));
        break;

      case "addNodeToGroup":
        this.stack.execute(new cmd.AddNodeToGroup(data.node, data.group));
        break;

      case "changeNode":
        if (!nodes.length) return;
        nodes.forEach((item) => {
          const oldData = item.getData();
          const cloneObject = zrender.util.clone(oldData);
          if (data.style) {
            cloneObject.style = { ...cloneObject.style, ...data.style };
          }

          if (data.shape) {
            cloneObject.shape = { ...cloneObject.shape, ...data.shape };
          }

          if (data.hasOwnProperty("z")) {
            cloneObject.z = parseInt(data.z);
          }

          const changed = new cmd.ChangeNode(item, oldData, cloneObject);
          cmds.push(changed);
        });
        const manycmds = new cmd.ManyCmd(cmds);
        this.stack.execute(manycmds);
        break;

      case "resizeNode":
        const _node = this.getSelectNodes()[0];
        const _data = _node.getData();
        const _changeNode = new cmd.ChangeNode(_node, data.oldData, _data);
        this.stack.execute(_changeNode);
        break;

      case "changeGroup":
        const { mark, group, style } = data;

        let oldData, newData;

        if (mark === "head") {
          oldData = { ...group.getHeadStyle() };
          newData = { ...oldData, ...style };
          this.stack.execute(new cmd.ChangeGroup(group, oldData, newData, mark));
        } else if (mark === "group") {
          oldData = { ...group.getGroupStyle() };
          newData = { ...oldData, ...style };
          this.stack.execute(new cmd.ChangeGroup(group, oldData, newData, mark));
        } else {
          this.stack.execute(new cmd.ChangeGroupHead(group, data.showHead));
        }
        break;

      case "deleteNode":
        this.stack.execute(new cmd.RemoveNodes([data.node], this));
        break;

      case "deleteNodes":
        this.stack.execute(new cmd.RemoveNodes(data.nodes, this));
        break;

      case "deleteGroup":
        this.stack.execute(new cmd.RemoveGroup(data.group, this));
        break;

      case "deleteEdge":
        this.stack.execute(new cmd.RemoveEdgeCmd(data.edge, this));
        break;

      case "changeEdge":
        const type = data.type;
        if (type === "changeAnchor") {
          if (data.mark === "anchor") {
            this.stack.execute(new cmd.ChangeEdgeAnchor(data.edge, data));
          } else if (data.mark === "point") {
            this.stack.execute(new cmd.ChangeEdgeAnchor(data.edge, data));
          }
        } else {
          if (data.mark === "descript") {
            var oldText = data.edge.getText();
            var newText = data.descript;
            this.stack.execute(new cmd.ChangeEdgeText(data.edge, oldText, newText));
          } else {
            var od, nd;
            od = data.edge.getData();
            nd = { ...od, ...data.style };
            this.stack.execute(new cmd.ChangeEdge(data.edge, od, nd));
          }
        }

        break;

      case "ungroup":
        if (data.group) {
          this.stack.execute(new cmd.CancelGroup(data.group, this));
        }
        break;
    }
  }

  createShape(shape) {
    this.execute("addShape", shape);
  }

  addNode(node) {
    this.nodes.push(node);
    this.add(node);

    if (!node.anchor) return;
    node.anchor.bars.forEach((bar) => {
      this.add(bar);
    });
  }

  moveNode(dx, dy, nodes) {
    const activeItems = nodes || this.getSelectNodes();

    activeItems.forEach((item) => {
      const [x, y] = item.oldPos;
      item.attr({ position: [x + dx, y + dy] });
      this.moveEdge(dx, dy, item);
    });
  }

  removeNode(node) {
    const index = this.nodes.indexOf(node);

    if (index === -1) return;
    this.nodes.splice(index, 1);

    node.anchor?.bars.forEach((bar) => {
      this.remove(bar);
    });

    this.remove(node);
  }

  addEdge(edge) {
    if (this.edges.indexOf(edge) === -1) {
      this.edges.push(edge);
      this.add(edge);
      edge.editor = this;
    }
  }

  moveEdge(dx, dy, node) {
    this.fullSelectEdges.forEach((item) => {
      const cpx = zrender.util.clone(item.oldCpx);
      cpx.x1 = cpx.x1 + dx;
      cpx.x2 = cpx.x2 + dx;
      cpx.y1 = cpx.y1 + dy;
      cpx.y2 = cpx.y2 + dy;
      cpx.cpx1 = cpx.cpx1 + dx;
      cpx.cpy1 = cpx.cpy1 + dy;
      cpx.cpx2 = cpx.cpx2 + dx;
      cpx.cpy2 = cpx.cpy2 + dy;
      if (item.type === "polyline") {
        const points = cpx.points;
        points.forEach((pos) => {
          pos[0] += dx;
          pos[1] += dy;
        });
        cpx.points = points;
        const center = cpx.lineCenter.slice();
        cpx.lineCenter[0] = center[0] + dx;
        cpx.lineCenter[1] = center[1] + dy;
      }
      item.setCpx(cpx);
      item.refresh();
    });

    this.notFullSelectEdges.forEach((item) => {
      if (node.nodeType === "group") node.create();

      node.createAnchors();
      if (item.fromNode === node) {
        item.setFromPoint(node.getAnchorsByIndex(item.fromAnchor.point.index));
        item.refresh();
      } else if (item.toNode === node) {
        item.setToPoint(node.getAnchorsByIndex(item.toAnchor.point.index));
        item.refresh();
      }
    });
  }

  removeEdge(edge) {
    const index = this.edges.indexOf(edge);

    if (index !== -1) {
      this.remove(edge);
      this.edges.splice(index, 1);
    }
  }

  createGroup() {
    const activeItems = this.getSelectNodes();

    if (activeItems.length <= 1) return;

    const hasGroup = activeItems.every((item) => !item.nodeGroup); // 判断是否有分组

    if (hasGroup) {
      const nodeGroup = new NodeGroup(activeItems);
      nodeGroup.editor = this;
      this.execute("createGroup", { group: nodeGroup });
    } else {
      const isFlag = activeItems.every((item) => item.nodeGroup); // 判断是否都在同一个分组
      if (!isFlag) return;

      const group = activeItems[0].nodeGroup;
      const sameFlagGroup = activeItems.every((n) => n.nodeGroup === group); // 判断是否都在同一个分组
      if (!sameFlagGroup) return;

      const zIndex = group.getLevel();
      const nodeGroup = new NodeGroup(activeItems, "新建分组", "#fafafa", "#ccc", "#333", "dash", zIndex + 1);
      nodeGroup.editor = this;
      nodeGroup.topGroup = group;
      this.execute("createGroup", { group: nodeGroup, topGroup: group });
    }
  }

  addGroup(group) {
    if (this.groups.indexOf(group) === -1) {
      this.add(group);

      group.anchor.bars.forEach((bar) => {
        this.add(bar);
      });

      this.groups.push(group);
      group.editor = this;
    }
  }

  removeGroup(group) {
    const index = this.groups.indexOf(group);

    if (index === -1) return;
    this.groups.splice(index, 1);

    this.remove(group);
    group.anchor.bars.forEach((bar) => {
      this.remove(bar);
    });
  }

  commandChange() {
    this.stack.change = () => this.refreshHistory();
  }

  viewProGroup() {
    //判断悬停时间
    const activeItems = this.getSelectNodes();

    activeItems.forEach((node) => {
      if (node.nodeGroup) {
        if (node.groupTime && node.groupEndTime) {
          if (node.groupEndTime - node.groupTime > 1000) {
            node.nodeGroup.viewTemGroup("down", node);
          }
        }
      } else {
        if (node.groupTime && node.groupEndTime && node.temporaryGroup) {
          if (node.groupEndTime - node.groupTime > 1000) {
            node.temporaryGroup.viewTemGroup("up", node);
          }
        }
      }
    });
  }

  multipleSelect() {
    const rect = this.selectFrame.getBoundingRect();
    const group = new zrender.Group();
    const [x, y] = this.position;
    this.nodes.forEach((item) => {
      const box = group.getBoundingRect([item]);
      box.x += x;
      box.y += y;

      if (rect.intersect(box)) {
        item.active();
      } else {
        item.unactive();
      }
    });
  }

  multipleSelectEdges(nodes) {
    this.selectGroups = [];
    this.fullSelectEdges = [];
    this.notFullSelectEdges = [];

    const activeItems = nodes || this.getSelectNodes();

    const edges = [];
    const fulledges = [];

    activeItems.forEach((item) => {
      switch (item.nodeType) {
        case "node":
          if (item.nodeGroup && this.selectGroups.indexOf(item.nodeGroup) === -1) {
            this.selectGroups.push(item.nodeGroup);
          }

          const _edges = this.getEdgesByNode(item);

          _edges.forEach((e) => {
            if (activeItems.indexOf(e.fromNode) > -1 && activeItems.indexOf(e.toNode) > -1) {
              if (fulledges.indexOf(e) === -1) {
                fulledges.push(e);
                e.oldCpx = { ...e.getCpx() };
                e.oldPos = e.position.slice();
              }
            } else {
              if (edges.indexOf(e) === -1) {
                edges.push(e);
                e.oldCpx = { ...e.getCpx() };
                e.oldPos = e.position.slice();
              }
            }
          });
          break;

        case "group":
          {
            const _edges = this.getEdgesByGroup(item);
            _edges.forEach((e) => {
              if (activeItems.indexOf(e.fromNode) > -1 && activeItems.indexOf(e.toNode) > -1) {
                if (fulledges.indexOf(e) === -1) {
                  fulledges.push(e);
                  e.oldCpx = { ...e.getCpx() };
                  e.oldPos = e.position.slice();
                }
              } else {
                if (edges.indexOf(e) === -1) {
                  edges.push(e);
                  e.oldCpx = { ...e.getCpx() };
                  e.oldPos = e.position.slice();
                }
              }
            });
          }
          break;
        default:
          break;
      }
    });

    this.fullSelectEdges = fulledges;
    this.notFullSelectEdges = edges;
  }

  showAnchor(x, y) {
    if (["selectEdge", "selectFrame"].includes(this.status)) return;

    const _group = new zrender.Group();
    const [posx, posy] = this.position;
    let num = 0;

    if (["isCreateLink", "changeEdge"].includes(this.status)) {
      num = 30;
    }

    const items = [...this.nodes, ...this.groups];

    items.forEach((item) => {
      if (item.dragging) {
        item.anchor?.hide();
        return;
      }
      const rect = _group.getBoundingRect([item]);
      rect.x = rect.x + posx - num;
      rect.y = rect.y + posy - num;
      rect.width = rect.width + num * 2;
      rect.height = rect.height + num * 2;

      if (rect.contain(x, y)) {
        item.anchor?.show();
      } else {
        item.anchor?.hide();
      }
    });
  }

  updateNodeAnchor() {
    const activeItems = this.getSelectNodes();

    activeItems.forEach((item) => {
      item.createAnchors();
      item.anchor?.refresh();
    });
  }

  undo() {
    this.stack.undo();
  }

  redo() {
    this.stack.redo();
  }

  copy(mark, item) {
    const nodes = [];
    const cmds = [];

    switch (mark) {
      case "node":
        item.forEach((_item) => {
          const [x, y] = _item.position;
          const data = zrender.util.clone(_item.getData());
          data.id = guid();
          const node = shape.getShape(data.command, data, false);
          nodes.push(node);
          node.attr({ position: [x + 20, y + 20] });
          node.createAnchors();
          node.anchor.refresh();
        });

        if (nodes.length) {
          const changed = new cmd.AddNodes(nodes, this);
          this.stack.execute(changed);
        }
        break;

      case "group":
        const cloneGroup = zrender.util.clone(item.getData());

        item.nodes.forEach((_item) => {
          if (_item.nodeType !== "node") return;

          const [x, y] = _item.position.slice();
          const data = zrender.util.clone(_item.getData());
          data.id = guid();
          const node = shape.getShape(data.command, data, false);
          nodes.push(node);
          node.attr({ position: [x + 20, y + 20] });
          node.createAnchors();
          node.anchor.refresh();
        });

        const group = new NodeGroup(nodes);
        group.editor = this;
        group.setGroupStyle(cloneGroup.groupStyle);
        group.setHeadStyle(cloneGroup.headStyle);
        group.changeHead(cloneGroup.showHead);
        const _group = new cmd.CreateGroup(group, null, this);
        const _groupchildren = new cmd.AddNodes(nodes, this);
        cmds.push(_group);
        cmds.push(_groupchildren);
        this.stack.execute(new cmd.ManyCmd(cmds));
        break;

      default:
        break;
    }
  }

  selectItem(node) {
    this.clearSelectItems();
    node.active();
    const group = new zrender.Group();
    const box = group.getBoundingRect([node]);
    this.frame.boxSelect(box, node);
    this.frame.refreshBar();
    this.frame.show();
  }

  clearHistory() {
    this.stack.clear();
  }

  getData() {
    const { id, nodes, groups, edges } = this;

    const node = nodes.map((item) => item.getData());
    const group = groups.map((item) => item.getData());
    const edge = edges.map((item) => item.getData());

    return { id, type: "cicadaFlow", node, group, edge };
  }

  getSelectItems() {
    return [...this.nodes.filter((item) => item.selected), ...this.edges.filter((item) => item.selected)];
  }

  getSelectNodes() {
    return this.nodes.filter((item) => item.selected || item.dragging);
  }

  getSelectEdges() {
    return this.edges.filter((item) => item.selected);
  }

  getEdgesByNode(node) {
    if (!node) return [];
    return this.edges.filter((edge) => edge.fromNode.id === node.id || edge.toNode.id === node.id);
  }

  getEdgesByGroup(group) {
    return this.edges.filter((edge) => edge.fromNode.id === group.id || edge.toNode.id === group.id);
  }

  getAllEdgesByGroup(group) {
    if (!group) return [];
    const edges = new Set(this.getEdgesByGroup(group));

    group.nodes?.forEach((node) => {
      this.getEdgesByNode(node).forEach((e) => {
        edges.add(e);
      });
    });

    return Array.from(edges);
  }

  getItemById(id) {
    return [...this.nodes, ...this.groups].find((item) => {
      if (!item.id) debugger;
      return item.getId() === id;
    });
  }

  getNodeById(id) {
    return this.nodes.find((item) => item.getId() === id);
  }

  getGroupById(id) {
    return this.groups.find((item) => item.getId() === id);
  }

  getEdgeById(id) {
    return this.edges.find((item) => item.getId() === id);
  }

  refreshHistory() {
    this.zr.trigger("undoredo", { undo: !!this.stack.canUndo(), redo: !!this.stack.canRedo() });
  }

  clearSelectItems() {
    this.nodes.forEach((item) => item.unactive());
    this.edges.forEach((item) => item.unactive());
    this.groups.forEach((g) => (g.selected = false));
    this.frame.hide();
  }
}

export default FlowEditor;
